﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace FATLib
{
    public class Device
    {
        private string volume_label;

        private Stream stream;
        private BinaryReader reader;
        private BinaryWriter writer;
        private BootSector boot_sector;
        private UInt32[] fat_table;

        private long directories = 0;
        private long files = 0;
        private long total_size = 0;
        private long total_space_used = 0;

        private FatEntry root_directory = new FatEntry(2);

        public Device(char drive_letter)
        {
            using (stream = new FileStream("f:\\auto_usb_image.img", FileMode.Open))
            //using(stream = new LowLevelDiskStream(drive_letter))
            using (reader = new BinaryReader(stream))
            using (writer = new BinaryWriter(stream))
            {
                LoadBootsector();
                LoadFATTable();
                Console.WriteLine("Filesystem info:");
                Console.WriteLine(boot_sector.ToString());
                Console.WriteLine("-------------------------------");

                ReadDirectory(root_directory, drive_letter + ":\\"); // 2 is el clustero primero :)

                Console.WriteLine("--------------------------");
                Console.WriteLine("Volume label: {0}", this.volume_label);
                Console.WriteLine("{0} directories, {1} files", this.directories, this.files);
                Console.WriteLine("Total filesize: {0}\r\nDisk space used: {1}", this.total_size, this.total_space_used);

                root_directory.Print();
                //root_directory.PrintSortedNess();
                //var test_dir = root_directory.Entries.First(x => x.GetFilename().Contains("Amanda Palmer")).Entries.First(x => x.GetFilename().Contains("Who"));
                //test_dir.Print();
                //SortDirectory(root_directory);
            }
        }

        private void LoadBootsector()
        {
            var ret = reader.BaseStream.Seek(0, SeekOrigin.Begin);
            var buff = new byte[512];
            reader.Read(buff, 0, buff.Length);
            boot_sector = new BootSector(buff);
        }

        private void LoadFATTable()
        {
            var ret = reader.BaseStream.Seek(boot_sector.GetFatStart(), SeekOrigin.Begin);
            var buff = new byte[boot_sector.GetFatSize()];
            reader.Read(buff, 0, buff.Length);

            if (boot_sector.fat == 32)
            {
                fat_table = new UInt32[buff.LongLength / 4];
                for (int i = 0; i < fat_table.LongLength; i++)
                {
                    fat_table[i] = BitConverter.ToUInt32(buff, i * 4);
                }
            }
            else
            {
                fat_table = new UInt32[buff.LongLength / 2];
                for (int i = 0; i < fat_table.LongLength; i++)
                {
                    fat_table[i] = BitConverter.ToUInt16(buff, i * 2);
                }
            }
        }

        private void ReadDirectory(FatEntry current_dir, string path)
        {
            /**
             * #1 seek to cluster
             * #2 read the cluster
             * #3 parse each entry
             * #4 for each dir run #1
             * #5 for each file read FAT to see how many clusters they use
             * #6 check FAT, if more clusters goto #1
             * */

            var cluster = new byte[boot_sector.bytes_per_sector * boot_sector.sectors_per_cluster];

            var next_cluster = current_dir.start_cluster;

            var lfn_buff = new List<byte[]>();
            byte lfn_checksum = 0;

            do
            {
                // #1
                reader.BaseStream.Position = ClusterToBytes(next_cluster);
                // #2
                reader.Read(cluster, 0, cluster.Length);
                // #3
                var entry_buff = new byte[32];
                for (long i = 0; i < cluster.Length / 32; i++)
                {
                    Array.Copy(cluster, i * 32, entry_buff, 0, entry_buff.Length);

                    if (entry_buff[0] == 0) continue;

                    if (FatEntry.IsValidLFNEntry(entry_buff))
                    {
                        lfn_buff.Add((byte[])entry_buff.Clone());
                        continue;
                    }
                    else if(FatEntry.IsDeletedLFNEntry(entry_buff)) continue;

                    var entry = new FatEntry(boot_sector.fat, entry_buff, lfn_buff);

                    if (entry.filename[0] == 0) break;

                    if (entry.IsVolumeLabel())
                    {
                        this.volume_label = entry.GetFilename();
                        continue;
                    }

                    if (entry.IsDeleted()) continue;
                    if (!IsValidCluster(entry.start_cluster)) continue;

                    lfn_buff.Clear();
                    lfn_checksum = 0;
                    if (!entry.IsVolumeLabel())//&& !entry.IsDeleted() )
                    {
                        current_dir.AddSubEntry(entry);
                        if (entry.IsDirectory())
                        {
                            // #4
                            if (new string[] { ".", ".." }.Contains(entry.GetFilename())) continue;
                            //Console.WriteLine( "d {0}{1}", path, Sane( entry.GetFilename() ) );
                            directories++;
                            ReadDirectory(entry, path + entry.GetFilename() + "\\");
                        }
                        else
                        {
                            //Console.WriteLine( "- {0}{1} ({2} bytes) (attr: {3:X2})", path, Sane( entry.GetFilename() ), entry.file_size, entry.attributes );
#if DEBUG_FILE_W
                                long old_position = reader.BaseStream.Position;
#endif
                            // #5
                            files++;
                            total_size += entry.file_size;

#if DEBUG_FILE_W
                                using (var f_writer = new BinaryWriter(new FileStream("e:\\test\\" + entry.long_filename, FileMode.Create)))
                                {
                                    var f_buff = new byte[boot_sector.bytes_per_sector * boot_sector.sectors_per_cluster];
#else
                            {
#endif
                                long f_next_cluster = entry.start_cluster;
                                int f_clusters = 0;
                                do
                                {
                                    //System.Diagnostics.Debug.Assert(fat_table.Length > f_next_cluster);
                                    f_clusters++;
#if DEBUG_FILE_W
                                        reader.BaseStream.Position = ClusterToBytes(next_cluster);
                                        reader.Read(f_buff, 0, f_buff.Length);
                                        f_writer.Write(f_buff, 0, (int)Math.Min(f_buff.LongLength, entry.file_size - f_writer.BaseStream.Position));
#endif
                                }
                                while (IsValidCluster(f_next_cluster = fat_table[f_next_cluster]));

                                total_space_used += f_clusters * boot_sector.sectors_per_cluster * boot_sector.bytes_per_sector;

                                //Console.WriteLine( "  Clusters: {0} ({1} bytes)", f_clusters, f_clusters * boot_sector.sectors_per_cluster * boot_sector.bytes_per_sector );
                            }
#if DEBUG_FILE_W
                                reader.BaseStream.Position = old_position;
#endif

                        }
                    }
                }


                // #6
            } while (IsValidCluster(next_cluster = fat_table[next_cluster]));

        }

        private void SortDirectory(FatEntry current_dir)
        {
            foreach (var dir in current_dir.Entries.Where(x => x.IsDirectory()))
            {
                SortDirectory(dir);
            }

            if (current_dir.Entries.Count(x => x.IsRegularFile()) == 0) return;

            if (current_dir.IsSortedCorrectly())
            {
                Console.WriteLine("<<Sorted correctly>> [{0}]", current_dir.GetFilename());
                return;
            }
            else
            {
                Console.WriteLine(">>Needs sorting<< [{0}]", current_dir.GetFilename());
            }
            var sorted = current_dir.GetSortedList();

            var new_cluster = new byte[boot_sector.GetClusterSize()];
            int pos = 0;

            foreach (var entry in sorted)
            {
                var entry_buff = entry.GetRawBuffer(true);
                Array.Copy(entry_buff, 0, new_cluster, pos, entry_buff.Length);
                pos += entry_buff.Length;

                Console.WriteLine("[{0}] ({1})", entry, entry_buff.Length);
            }

            writer.BaseStream.Position = ClusterToBytes(current_dir.start_cluster);
            writer.Write(new_cluster);


        }

        private static bool IsValidCluster(long val)
        {
            return val > 1 && val < 0x0FFFFFF8;
        }

        private long ClusterToBytes(long cluster_number)
        {
            return ((cluster_number - 2) * boot_sector.sectors_per_cluster + boot_sector.GetSizeOfSystemAreaInSectors()) * boot_sector.bytes_per_sector;
        }

        private static string Sane(string inp)
        {
            return inp == null ? null : inp.Replace('\0', '0');
        }

    }
}
